/* gap.c, Ait, BSD 3-Clause, Kevin Bloom, 2023,
   Derived from: Atto January 2017
   Derived from: Anthony's Editor January 93
*/

#include <sys/stat.h>
#include "header.h"
#include "util.h"

/* Enlarge gap by n chars, position of gap cannot change */
int growgap(buffer_t *bp, point_t n)
{
  char_t *new;
  point_t buflen, newlen, xgap, xegap;

  assert(bp->b_buf <= bp->b_gap);
  assert(bp->b_gap <= bp->b_egap);
  assert(bp->b_egap <= bp->b_ebuf);

  xgap = bp->b_gap - bp->b_buf;
  xegap = bp->b_egap - bp->b_buf;
  buflen = bp->b_ebuf - bp->b_buf;

  /* reduce number of reallocs by growing by a minimum amount */
  n = (n < MIN_GAP_EXPAND ? MIN_GAP_EXPAND : n);
  newlen = buflen + n * sizeof (char_t);

  if (buflen == 0) {
    if (newlen < 0 || MAX_SIZE_T < newlen)
      fatal("%s: Failed to allocate required memory.\n");
    new = (char_t*) malloc((size_t) newlen);
    if (new == NULL)
      fatal("%s: Failed to allocate required memory.\n");  /* Cannot edit a file without a buffer. */
  } else {
    if (newlen < 0 || MAX_SIZE_T < newlen) {
      msg("Failed to allocate required memory");
      return (FALSE);
    }
    new = (char_t*) realloc(bp->b_buf, (size_t) newlen);
    if (new == NULL) {
      msg("Failed to allocate required memory");    /* Report non-fatal error. */
      return (FALSE);
    }
  }

  /* Relocate pointers in new buffer and append the new
   * extension to the end of the gap.
   */
  bp->b_buf = new;
  bp->b_gap = bp->b_buf + xgap;
  bp->b_ebuf = bp->b_buf + buflen;
  bp->b_egap = bp->b_buf + newlen;
  while (xegap < buflen--)
    *--bp->b_egap = *--bp->b_ebuf;
  bp->b_ebuf = bp->b_buf + newlen;

  assert(bp->b_buf < bp->b_ebuf);          /* Buffer must exist. */
  assert(bp->b_buf <= bp->b_gap);
  assert(bp->b_gap < bp->b_egap);          /* Gap must grow only. */
  assert(bp->b_egap <= bp->b_ebuf);
  return (TRUE);
}

point_t movegap(buffer_t *bp, point_t offset)
{
  char_t *p = ptr(bp, offset);
  while (p < bp->b_gap)
    *--bp->b_egap = *--bp->b_gap;
  while (bp->b_egap < p)
    *bp->b_gap++ = *bp->b_egap++;
  assert(bp->b_gap <= bp->b_egap);
  assert(bp->b_buf <= bp->b_gap);
  assert(bp->b_egap <= bp->b_ebuf);
  return (pos(bp, bp->b_egap));
}

/* Given a buffer offset, convert it to a pointer into the buffer */
char_t * ptr(buffer_t *bp, register point_t offset)
{
  if (offset < 0)
    return (bp->b_buf);
  return (bp->b_buf+offset + (bp->b_buf + offset < bp->b_gap ? 0 : bp->b_egap-bp->b_gap));
}

/* Given a pointer into the buffer, convert it to a buffer offset */
point_t pos(buffer_t *bp, register char_t *cp)
{
  assert(bp->b_buf <= cp && cp <= bp->b_ebuf);
  return (cp - bp->b_buf - (cp < bp->b_egap ? 0 : bp->b_egap - bp->b_gap));
}

int posix_file(char *fn)
{
  if (fn[0] == '_')
    return (FALSE);

  for (; *fn != '\0'; ++fn) {
    if (!isalnum(*fn) && *fn != '.' && *fn != '_' && *fn != '-' && *fn != '/')
      return (FALSE);
  }
  return (TRUE);
}

void relocate_backup(char *fp)
{
  char ltemp[NAME_MAX+2];
  strcpy(ltemp, fp);
  replace_all(ltemp, '/', '!');
  strcpy(fp, BACKUP_DIR);
  strcat(fp, ltemp);
}

/*
   TODO: make the location of backup files an option. Either put them in the dir
   of the file or in `backup_dir`.
*/
int backup_file(char *fn)
{
  FILE *orig, *backup;
  char bfn[NAME_MAX+2];
  char ch;
  strcpy(bfn, fn);
  if(BACKUP_DIR != NULL)
    relocate_backup(bfn);
  strcat(bfn, "~");
//  printf("%s", bfn);
  orig = fopen(fn, "r");
  if(orig == NULL) {
    msg("Failed to open original file \"%s\".", fn);
    return -1;
  }
  backup = fopen(bfn, "w");
  if(backup == NULL) {
    msg("Failed to open backup file.");
    return -1;
  }
  while((ch = fgetc(orig)) != EOF)
    fputc(ch, backup);
  fclose(orig);
  fclose(backup);
  return 1;
}

int save(char *fn)
{
  FILE *fp;
  point_t length;
  struct stat sb;

//  if (!posix_file(fn)) {
//    msg("Not a portable POSIX file name.");
//    return (FALSE);
//  }
  stat(fn, &sb);
  if (!backup_file(fn)) {
    msg("Failed to backup file \"%s\".", fn);
    return (FALSE);
  }
  fp = fopen(fn, "w");
  if (fp == NULL) {
    msg("Failed to open file \"%s\".", fn);
    return (FALSE);
  }
  (void) movegap(curbp, (point_t) 0);
  length = (point_t) (curbp->b_ebuf - curbp->b_egap);
  if (fwrite(curbp->b_egap, sizeof (char), (size_t) length, fp) != length) {
    msg("Failed to write file \"%s\".", fn);
    return (FALSE);
  }
  if (fclose(fp) != 0) {
    msg("Failed to close file \"%s\".", fn);
    return (FALSE);
  }
  curbp->b_flags &= ~B_MODIFIED;
  if (stat(fn, &sb) < 0) {
    msg("Failed to find file \"%s\".", fn);
    return (FALSE);
  }
  if (MAX_SIZE_T < sb.st_size) {
    msg("File \"%s\" is too big to load.", fn);
    return (FALSE);
  }
  curbp->b_fmtime = sb.st_mtime;
  msg("File \"%s\" %ld bytes saved.", fn, pos(curbp, curbp->b_ebuf));
  return (TRUE);
}

int load_file(char *fn)
{
  /* reset the gap, make it the whole buffer */
  curbp->b_gap = curbp->b_buf;
  curbp->b_egap = curbp->b_ebuf;
  top();
  return insert_file(fn, FALSE);
}

/* reads file into buffer at point */
int insert_file(char *fn, int modflag)
{
  FILE *fp;
  size_t len;
  struct stat sb;

  if (stat(fn, &sb) < 0) {
    msg("Failed to find file \"%s\".", fn);
    return (FALSE);
  }
  if (MAX_SIZE_T < sb.st_size) {
    msg("File \"%s\" is too big to load.", fn);
    return (FALSE);
  }
  if (curbp->b_egap - curbp->b_gap < sb.st_size * sizeof (char_t) && !growgap(curbp, sb.st_size))
    return (FALSE);
  if ((fp = fopen(fn, "r")) == NULL) {
    msg("Failed to open file \"%s\".", fn);
    return (FALSE);
  }
  curbp->b_point = movegap(curbp, curbp->b_point);
//  undoset();
  curbp->b_gap += len = fread(curbp->b_gap, sizeof (char), (size_t) sb.st_size, fp);

  if (fclose(fp) != 0) {
    msg("Failed to close file \"%s\".", fn);
    return (FALSE);
  }
  curbp->b_flags &= (modflag ? B_MODIFIED : ~B_MODIFIED);
  curbp->b_fmtime = sb.st_mtime;
  msg("%ld bytes read.", len);
  return (TRUE);
}

/* Record a new undo location */
void undoset()
{
  point_t length;
  if(curbp->b_undo == NULL) {
    curbp->b_undo = realloc(curbp->b_undo, sizeof(undo_t));
    curbp->b_eundo = curbp->b_undo;
  }
  curbp->b_eundo += sizeof(undo_t);
//  if(pos(curbp, curbp->b_gap) == 0)
  (void) movegap(curbp, curbp->b_point);
  curbp->b_eundo->u_point = curbp->b_point;
  curbp->b_eundo->u_gap = curbp->b_gap - curbp->b_buf;
  curbp->b_eundo->u_egap = curbp->b_egap - curbp->b_buf;
  (void) movegap(curbp, (point_t) 0);
  length = (point_t) (curbp->b_ebuf - curbp->b_egap);
  curbp->b_eundo->u_size = length;
  curbp->b_eundo->u_scrap = malloc(sizeof(char_t)*curbp->b_eundo->u_size);
  memcpy(curbp->b_eundo->u_scrap, curbp->b_egap, curbp->b_eundo->u_size);
  (void) movegap(curbp, curbp->b_eundo->u_point);
  curbp->b_gap = curbp->b_eundo->u_gap + curbp->b_buf;
  curbp->b_egap = curbp->b_eundo->u_egap + curbp->b_buf;
}

void redoset()
{
  point_t length;
  if(curbp->b_redo == NULL) {
    curbp->b_redo = realloc(curbp->b_redo, sizeof(undo_t));
    curbp->b_eredo = curbp->b_redo;
  }
  curbp->b_eredo += sizeof(undo_t);
  (void) movegap(curbp, curbp->b_point);
  curbp->b_eredo->u_point = curbp->b_point;
  curbp->b_eredo->u_gap = curbp->b_gap - curbp->b_buf;
  curbp->b_eredo->u_egap = curbp->b_egap - curbp->b_buf;
  (void) movegap(curbp, (point_t) 0);
  length = (point_t) (curbp->b_ebuf - curbp->b_egap);
  curbp->b_eredo->u_size = length;
  curbp->b_eredo->u_scrap = malloc(sizeof(char_t)*curbp->b_eredo->u_size);
  memcpy(curbp->b_eredo->u_scrap, curbp->b_egap, curbp->b_eredo->u_size);
  (void) movegap(curbp, curbp->b_eredo->u_point);
  curbp->b_gap = curbp->b_eredo->u_gap + curbp->b_buf;
  curbp->b_egap = curbp->b_eredo->u_egap + curbp->b_buf;
}

/* Undo */
void undo()
{
  if(curbp->b_eundo == curbp->b_undo) {
    msg("Nothing to undo!");
    return;
  }
  redoset();
  curbp->b_gap = curbp->b_buf + curbp->b_eundo->u_gap;
  curbp->b_egap = curbp->b_buf + curbp->b_eundo->u_egap;
  (void) movegap(curbp, (point_t) 0);
  memcpy(curbp->b_egap, curbp->b_eundo->u_scrap, curbp->b_eundo->u_size);
  curbp->b_point = movegap(curbp, curbp->b_eundo->u_point);
  curbp->b_flags |= B_MODIFIED;
  curbp->b_eundo -= sizeof(undo_t);
  curwp->w_update = TRUE;
}

/* Redo */
void redo()
{
  if(curbp->b_eredo == curbp->b_redo) {
    msg("Nothing to redo!");
    return;
  }
  undoset();
  curbp->b_gap = curbp->b_buf + curbp->b_eredo->u_gap;
  curbp->b_egap = curbp->b_buf + curbp->b_eredo->u_egap;
  (void) movegap(curbp, (point_t) 0);
  memcpy(curbp->b_egap, curbp->b_eredo->u_scrap, curbp->b_eredo->u_size);
  curbp->b_point = movegap(curbp, curbp->b_eredo->u_point);
  curbp->b_flags |= B_MODIFIED;
  curbp->b_eredo -= sizeof(undo_t);
  curwp->w_update = TRUE;
}


/* find the point for start of line ln */
point_t line_to_point(int ln)
{
  point_t end_p = pos(curbp, curbp->b_ebuf);
  point_t p, start;
  for (p=0, start=0; p <= end_p; p++) {
    char_t *c = ptr(curbp, p);
    if(c == 0)
      break;
    if ( *c == '\n') {
      if (--ln == 0)
        return start;
      if (p + 1 <= end_p)
        start = p + 1;
    }
    if(!*c && ln == 1)
      return start;
  }
  return -1;
}

/* scan buffer and fill in curline and lastline */
void get_line_stats(int *curline, int *lastline, buffer_t *bp)
{
  point_t end_p = pos(bp, bp->b_ebuf);
  point_t p;
  int line;

  *curline = -1;

  for (p=0, line=0; p < end_p; p++) {
    line += (*(ptr(bp,p)) == '\n') ? 1 : 0;
    *lastline = line;

    if (*curline == -1 && p == bp->b_point) {
      *curline = (*(ptr(bp,p)) == '\n') ? line : line + 1;
    }
  }

  *lastline = *lastline + 1;

  if (bp->b_point == end_p)
    *curline = *lastline;
}

/* Return TRUE if file was modified after the current buffer's recored mtime. */
int is_file_modified(char *fn)
{
  struct stat sb;

  if(stat(fn, &sb) < 0) {
    return (FALSE);
  }
  if(sb.st_mtime != 0 && curbp->b_fmtime != sb.st_mtime) {
    return (TRUE);
  }
  return (FALSE);
}

/* Return TRUE to continue, FALSE to stop. Revert happens here. */
int file_was_modified_prompt()
{
  const char *prompt = "This file has changed on disk; really edit (y/N/r) ?";
  char c = '\0';
  point_t p = curbp->b_point;
  struct stat sb;
  print_to_msgline(prompt);
  clrtoeol(prompt, MSGLINE);
  c = yesnomaybeso('n');
  if (c == 'n') {
    clrtoeol("", MSGLINE);
    return FALSE;
  } else if(c == 'r') {
    curbp->b_point = 0;
    load_file(curbp->b_fname);
    clrtoeol("", MSGLINE);
    curbp->b_point = p;
    curwp->w_update = TRUE;
    msg("Buffer reverted.");
    return FALSE;
  } else if(c == 'y') {
    clrtoeol("", MSGLINE);
    if (stat(curbp->b_fname, &sb) < 0) {
      msg("Failed to find file \"%s\".", curbp->b_fname);
      return (FALSE);
    }
    if (MAX_SIZE_T < sb.st_size) {
      msg("File \"%s\" is too big to load.", curbp->b_fname);
      return (FALSE);
    }
    curbp->b_fmtime = sb.st_mtime;
    return TRUE;
  }
  clrtoeol("", MSGLINE);
  return (FALSE);
}
